// =====================================================================
//
//  This file is part of the Microsoft Dynamics CRM SDK code samples.
//
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//  This source code is intended only as a supplement to Microsoft
//  Development Tools and/or on-line documentation.  See these other
//  materials for detailed information regarding Microsoft code samples.
//
//  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//
// =====================================================================
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Text;

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

namespace CRMCustom
{
	using CrmSdk;

	public static class RegistrationHelper
	{
		/// <summary>
		/// Extracts all Workflow Activities and Plugins from the Assembly
		/// </summary>
		/// <param name="pathToAssembly">Path to the DLL file that is needed to gather information</param>
		/// <returns>List of Plugins in the Assembly</returns>
		public static CrmPluginAssembly RetrievePluginsFromAssembly(string pathToAssembly)
		{
			using (AppDomainContext<AssemblyReader> context = new AppDomainContext<AssemblyReader>())
			{
				return context.Proxy.RetrievePluginsFromAssembly(pathToAssembly);
			}
		}

		/// <summary>
		/// Extracts the properties for the plugin assembly
		/// </summary>
		/// <param name="pathToAssembly">Path to the DLL file that is needed to gather information</param>
		/// <returns>Properties Assembly</returns>
		public static CrmPluginAssembly RetrieveAssemblyProperties(string pathToAssembly)
		{
			using (AppDomainContext<AssemblyReader> context = new AppDomainContext<AssemblyReader>())
			{
				return context.Proxy.RetrieveAssemblyProperties(pathToAssembly);
			}
		}

		/// <summary>
		/// Generates a step description for the given message, primary and secondary entities, for a Step
		/// </summary>
		/// <param name="typeName">Name of the plug-in type</param>
		/// <param name="messageName">Message Name</param>
		/// <param name="primaryEntity">Primary Entity</param>
		/// <param name="secondaryEntity">Secondary Entity</param>
		/// <returns>Description that the Step should use</returns>
		public static string GenerateStepDescription(string typeName, string messageName, string primaryEntity, string secondaryEntity)
		{
			StringBuilder descriptionBuilder = new StringBuilder();
			if (!string.IsNullOrWhiteSpace(typeName))
			{
				descriptionBuilder.AppendFormat("{0}: ", typeName);
			}

			if (string.IsNullOrEmpty(messageName))
			{
				descriptionBuilder.Append("Not Specified of ");
			}
			else
			{
				descriptionBuilder.AppendFormat("{0} of ", messageName);
			}

			bool hasPrimaryEntity = false;
			if (!string.IsNullOrEmpty(primaryEntity) &&
				!string.Equals(primaryEntity, "none", StringComparison.InvariantCultureIgnoreCase))
			{
				hasPrimaryEntity = true;
				descriptionBuilder.Append(primaryEntity);
			}

			if (!string.IsNullOrEmpty(secondaryEntity) &&
				!string.Equals(secondaryEntity, "none", StringComparison.InvariantCultureIgnoreCase))
			{
				string format;
				if (hasPrimaryEntity)
				{
					format = "and {0}";
				}
				else
				{
					format = "{0}";
				}

				descriptionBuilder.AppendFormat(format, secondaryEntity);
			}
			else if (!hasPrimaryEntity)
			{
				descriptionBuilder.Append(" any Entity");
			}

			return descriptionBuilder.ToString();
		}

		/// <summary>
		/// Generate the default group name for an activity
		/// </summary>
		public static string GenerateDefaultGroupName(string assemblyName, Version version)
		{
			if (string.IsNullOrWhiteSpace(assemblyName))
			{
				throw new ArgumentNullException("assemblyName");
			}
			else if (null == version)
			{
				throw new ArgumentNullException("version");
			}

			return string.Format(CultureInfo.InvariantCulture, "{0} ({1})", assemblyName, version);
		}

		#region Check for Existance Methods
		/// <summary>
		/// Determines if the given TypeName is in use for the given assembly id
		/// </summary>
		/// <param name="org">Organization to check</param>
		/// <param name="typeName">Type Name to check</param>
		/// <param name="pluginAssemblyId">Assembly that should contain the Type</param>
		/// <param name="pluginTypeId">pluginTypeId that is using the type name</param>
		/// <returns>True if the type name is found</returns>
		public static bool PluginExists(CrmOrganization org, string typeName, Guid pluginAssemblyId,
			out Guid pluginTypeId)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (typeName == null)
			{
				throw new ArgumentNullException("typeName");
			}

			QueryByAttribute query = new QueryByAttribute(PluginType.EntityLogicalName);
			query.ColumnSet = new ColumnSet();
			query.AddAttributeValue("typename", typeName);
			if (pluginAssemblyId != Guid.Empty)
			{
				query.AddAttributeValue("pluginassemblyid", pluginAssemblyId);
			}

			EntityCollection results = org.OrganizationService.RetrieveMultipleAllPages(query);
			if (results.Entities != null && results.Entities.Count > 0)
			{
				pluginTypeId = results.Entities[0].Id;
				return true;
			}
			else
			{
				pluginTypeId = Guid.Empty;
				return false;
			}
		}
		#endregion

		#region Registration, Update, Unregister Methods
		public static Guid RegisterServiceEndpoint(CrmOrganization org, CrmServiceEndpoint serviceEndpoint)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (serviceEndpoint == null)
			{
				throw new ArgumentNullException("serviceEndpoint");
			}
			ServiceEndpoint sep = serviceEndpoint.GenerateCrmEntities()[ServiceEndpoint.EntityLogicalName] as ServiceEndpoint;

			return org.OrganizationService.Create(sep);
		}
		public static void UpdateServiceEndpoint(CrmOrganization org, CrmServiceEndpoint serviceEndpoint)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (serviceEndpoint == null)
			{
				throw new ArgumentNullException("serviceEndpoint");
			}
			ServiceEndpoint sep = serviceEndpoint.GenerateCrmEntities()[ServiceEndpoint.EntityLogicalName] as ServiceEndpoint;

			org.OrganizationService.Update(sep);
			OrganizationHelper.RefreshServiceEndpoint(org, serviceEndpoint);
		}

		public static Guid RegisterAssembly(CrmOrganization org, string pathToAssembly, CrmPluginAssembly assembly)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (assembly == null)
			{
				throw new ArgumentNullException("assembly");
			}
			else if (assembly.SourceType == CrmAssemblySourceType.Database && pathToAssembly == null)
			{
				throw new ArgumentNullException("pathToAssembly", "Cannot be null when SourceType is Database");
			}

			PluginAssembly ptl = (PluginAssembly)assembly.GenerateCrmEntities()[PluginAssembly.EntityLogicalName];
			if (assembly.SourceType == CrmAssemblySourceType.Database)
			{
				ptl.Content = Convert.ToBase64String(File.ReadAllBytes(pathToAssembly));
			}

			return org.OrganizationService.Create(ptl);
		}

		/// <summary>
		/// Assembly is Uploaded if it is database. 
		/// We dont do Smart updates
		/// </summary>
		/// <param name="org"></param>
		/// <param name="pathToAssembly"></param>
		/// <param name="assembly"></param>
		public static void UpdateAssembly(CrmOrganization org, string pathToAssembly, CrmPluginAssembly assembly,
			params PluginType[] type)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (assembly == null)
			{
				throw new ArgumentNullException("assembly");
			}

			PluginAssembly ptl = (PluginAssembly)assembly.GenerateCrmEntities()[PluginAssembly.EntityLogicalName];

			//If the assembly path is not set, then the content does not need to be updated
			if (!string.IsNullOrEmpty(pathToAssembly) &&
				assembly.SourceType == CrmAssemblySourceType.Database)
			{
				ptl.Content = Convert.ToBase64String(File.ReadAllBytes(pathToAssembly));
			}

			if (null != type && 0 != type.Length)
			{
				ptl.pluginassembly_plugintype = type;
			}

			org.OrganizationService.Update(ptl);
			OrganizationHelper.RefreshAssembly(org, assembly);
		}

		public static void UpdateAssembly(CrmOrganization org, string description, Guid assemblyId)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (assemblyId == Guid.Empty)
			{
				throw new ArgumentNullException("assemblyId");
			}

			// Work around as updating only description is failing with publickeytoken not null
			PluginAssembly pt1 = org.OrganizationService.Retrieve(PluginAssembly.EntityLogicalName, assemblyId, new ColumnSet(true)) as PluginAssembly;
			//PluginAssembly pt1 = new PluginAssembly();
			pt1.Description = description;
			org.OrganizationService.Update(pt1);
		}
		public static Guid RegisterPlugin(CrmOrganization org, CrmPlugin plugin)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (plugin == null)
			{
				throw new ArgumentNullException("plugin");
			}

			PluginType pt = (PluginType)plugin.GenerateCrmEntities()[PluginType.EntityLogicalName];

			return org.OrganizationService.Create(pt);
		}

		public static void UpdatePluginFriendlyName(CrmOrganization org, Guid pluginId, string friendlyName)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (pluginId == Guid.Empty)
			{
				throw new ArgumentException("Invalid Guid", "pluginId");
			}
			else if (friendlyName == null)
			{
				//No updates will occur if friendly name is null. Don't need to do anything
				return;
			}

			PluginType updatePlugin = new PluginType();

			updatePlugin.PluginTypeId = new Guid?();
			updatePlugin["plugintypeid"] = pluginId;

			updatePlugin.FriendlyName = friendlyName;

			org.OrganizationService.Update(updatePlugin);
			return;
		}

		public static void UpdatePlugin(CrmOrganization org, CrmPlugin plugin)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (plugin == null)
			{
				throw new ArgumentNullException("plugin");
			}

			PluginType ptl = (PluginType)plugin.GenerateCrmEntities()[PluginType.EntityLogicalName];

			org.OrganizationService.Update(ptl);
			OrganizationHelper.RefreshPlugin(org, plugin);
		}
		public static void UpdatePlugin(CrmOrganization org, Guid pluginId, string typeName, string friendlyName)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			if (pluginId == Guid.Empty)
			{
				throw new ArgumentException("Invalid Guid", "pluginId");
			}
			if (string.IsNullOrEmpty(typeName))
			{
				throw new ArgumentException("Invalid typeName", "typeName");
			}

			if (friendlyName == null)
			{
				//No updates will occur if friendly name is null. Don't need to do anything
				return;
			}

			PluginType updatePlugin = new PluginType();

			updatePlugin.PluginTypeId = new Guid?();
			updatePlugin["plugintypeid"] = pluginId;

			updatePlugin.FriendlyName = friendlyName;
			updatePlugin.TypeName = typeName;
			org.OrganizationService.Update(updatePlugin);
			return;
		}

		public static Guid RegisterStep(CrmOrganization org, CrmPluginStep step)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (step == null)
			{
				throw new ArgumentNullException("step");
			}

			Dictionary<string, object> entityList = step.GenerateCrmEntities();
			SdkMessageProcessingStep sdkStep = (SdkMessageProcessingStep)entityList[SdkMessageProcessingStep.EntityLogicalName];

			//This is a sanity check. The UI won't allow a user to set the secure configuration.
			if (org.SecureConfigurationPermissionDenied)
			{
				sdkStep.Attributes.Remove("sdkmessageprocessingstepsecureconfigid");
				sdkStep.RelatedEntities.Clear();
			}
			else if (entityList.ContainsKey(CrmSdk.SdkMessageProcessingStepSecureConfig.EntityLogicalName))
			{
				Guid secureConfigId = Guid.NewGuid();

				//Create the related secure config in the related entities
				SdkMessageProcessingStepSecureConfig sdkSecureConfig =
					(SdkMessageProcessingStepSecureConfig)entityList[SdkMessageProcessingStepSecureConfig.EntityLogicalName];
				sdkSecureConfig.Id = secureConfigId;
				sdkStep.RelatedEntities[new Relationship(CrmPluginStep.RelationshipStepToSecureConfig)] =
					new EntityCollection(new Entity[] { sdkSecureConfig }) { EntityName = sdkSecureConfig.LogicalName };
				step.SecureConfigurationId = secureConfigId;
			}

			return org.OrganizationService.Create(sdkStep);
		}

		public static void UpdateStep(CrmOrganization org, CrmPluginStep step, Guid? origSecureConfigId)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (step == null)
			{
				throw new ArgumentNullException("step");
			}

			Dictionary<string, object> entityList = step.GenerateCrmEntities();
			SdkMessageProcessingStep sdkStep = (SdkMessageProcessingStep)entityList[SdkMessageProcessingStep.EntityLogicalName];

			//This is a sanity check. The UI won't allow a user to set the secure configuration.
			if (org.SecureConfigurationPermissionDenied)
			{
				sdkStep.Attributes.Remove("sdkmessageprocessingstepsecureconfigid");
				sdkStep.RelatedEntities.Clear();
				origSecureConfigId = null;
			}
			else if (entityList.ContainsKey(CrmSdk.SdkMessageProcessingStepSecureConfig.EntityLogicalName))
			{
				if (null == origSecureConfigId)
				{
					entityList.Remove(CrmSdk.SdkMessageProcessingStepSecureConfig.EntityLogicalName);
				}
				else
				{
					SdkMessageProcessingStepSecureConfig sdkSecureConfig =
						(SdkMessageProcessingStepSecureConfig)entityList[SdkMessageProcessingStepSecureConfig.EntityLogicalName];

					Guid secureConfigId;
					EntityState secureConfigState;
					if (step.SecureConfigurationId == origSecureConfigId && origSecureConfigId.GetValueOrDefault() != Guid.Empty)
					{
						//Set the ID of the secure configuration to be the 
						secureConfigId = origSecureConfigId.GetValueOrDefault();
						secureConfigState = EntityState.Changed;

						//Set the original secure config id so that the current id is not deleted
						sdkStep.SdkMessageProcessingStepSecureConfigId =
							new EntityReference(SdkMessageProcessingStepSecureConfig.EntityLogicalName, secureConfigId);
					}
					else
					{
						secureConfigId = Guid.NewGuid();
						secureConfigState = EntityState.Created;
					}

					//Set the configuration id for the step
					step.SecureConfigurationId = secureConfigId;

					//Populate the secure configuration object and add it to the related entities
					sdkSecureConfig.SdkMessageProcessingStepSecureConfigId = secureConfigId;
					sdkSecureConfig.EntityState = secureConfigState;

					//Update the related entities
					sdkStep.sdkmessageprocessingstepsecureconfigid_sdkmessageprocessingstep = sdkSecureConfig;

					//Reset the original secure configuration
					origSecureConfigId = null;
				}
			}
			else if (null != origSecureConfigId)
			{
				// To null out if it was set before
				sdkStep.SdkMessageProcessingStepSecureConfigId = null;
				step.SecureConfigurationId = Guid.Empty;

				if (Guid.Empty == origSecureConfigId)
				{
					origSecureConfigId = null;
				}
			}

			//Update the step
			org.OrganizationService.Update(sdkStep);
			OrganizationHelper.RefreshStep(org, step);

			// Delete the orphaned Secure config when nulling out the value on the step
			if (null != origSecureConfigId)
			{
				org.OrganizationService.Delete(SdkMessageProcessingStepSecureConfig.EntityLogicalName, origSecureConfigId.GetValueOrDefault());
			}
		}

		public static void UpdateStepStatus(CrmOrganization org, Guid stepId, bool isEnable)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (stepId == Guid.Empty)
			{
				throw new ArgumentException("Invalid Guid", "stepId");
			}

			Microsoft.Crm.Sdk.Messages.SetStateRequest request = new Microsoft.Crm.Sdk.Messages.SetStateRequest();
			request.EntityMoniker = new EntityReference(SdkMessageProcessingStep.EntityLogicalName, stepId);
			if (isEnable)
			{
				request.State = new OptionSetValue((int)SdkMessageProcessingStepState.Enabled);
			}
			else
			{
				request.State = new OptionSetValue((int)SdkMessageProcessingStepState.Disabled);
			}
			request.Status = new OptionSetValue(-1);
			org.OrganizationService.Execute(request);

			return;
		}

		public static void UpdateStepDescription(CrmOrganization org, Guid stepId, string description)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (stepId == Guid.Empty)
			{
				throw new ArgumentException("Invalid Guid", "stepId");
			}
			else if (description == null)
			{
				//No updates will occur if description is null. Don't need to do anything
				return;
			}

			SdkMessageProcessingStep updateStep = new SdkMessageProcessingStep();

			updateStep.SdkMessageProcessingStepId = new Guid?();
			updateStep["sdkmessageprocessingstepid"] = stepId;

			updateStep.Description = description;

			org.OrganizationService.Update(updateStep);
			return;
		}

		public static Guid RegisterImage(CrmOrganization org, CrmPluginImage image)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (image == null)
			{
				throw new ArgumentNullException("image");
			}
			return RegisterImage(org, image, org[image.AssemblyId][image.PluginId][image.StepId]);
		}

		public static Guid RegisterImage(CrmOrganization org, CrmPluginImage image, CrmPluginStep step)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			if (image == null)
			{
				throw new ArgumentNullException("image");
			}
			if (step == null)
			{
				throw new ArgumentNullException("step");
			}

			Dictionary<string, object> entityList = image.GenerateCrmEntities(step.MessageId, step.MessageEntityId);
			SdkMessageProcessingStepImage image1 = (SdkMessageProcessingStepImage)entityList[SdkMessageProcessingStepImage.EntityLogicalName];

			return org.OrganizationService.Create(image1);

		}

		public static void UpdateImage(CrmOrganization org, CrmPluginImage image)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			if (image == null)
			{
				throw new ArgumentNullException("image");
			}
			UpdateImage(org, image, org[image.AssemblyId][image.PluginId][image.StepId]);
		}

		public static void UpdateImage(CrmOrganization org, CrmPluginImage image, CrmPluginStep step)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			if (image == null)
			{
				throw new ArgumentNullException("image");
			}
			if (step == null)
			{
				throw new ArgumentNullException("step");
			}

			Dictionary<string, object> entityList = image.GenerateCrmEntities(step.MessageId, step.MessageEntityId);
			org.OrganizationService.Update((SdkMessageProcessingStepImage)entityList[CrmSdk.SdkMessageProcessingStepImage.EntityLogicalName]);
			OrganizationHelper.RefreshImage(org, image, step);
		}

		/// <summary>
		/// Unregister entities
		/// </summary>
		/// <param name="org">Organization to be used</param>
		/// <param name="cascadeOperation">Cascade the operation</param>
		/// <param name="crmEntity">Entities to be unregistered. If these are not invalid entities, only one can be specified</param>
		/// <returns>Statistics of what was unregistered</returns>
		public static Dictionary<string, int> Unregister(CrmOrganization org, params ICrmEntity[] crmEntity)
		{
			return Unregister(org, null, crmEntity);
		}

		/// <summary>
		/// Unregister entities and cascade the operation
		/// </summary>
		/// <param name="org">Organization to be used</param>
		/// <param name="crmEntity">Entities to be unregistered. If these are not invalid entities, only one can be specified</param>
		/// <param name="cascadeOperation">Cascade the operation</param>
		/// <param name="prog">ProgressIndicator to indicate success</param>
		/// <returns>Statistics of what was unregistered</returns>
		public static Dictionary<string, int> Unregister(CrmOrganization org, ProgressIndicator prog, params ICrmEntity[] crmEntity)
		{
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (crmEntity == null || crmEntity.Length == 0)
			{
				throw new ArgumentNullException("crmEntity");
			}

			Collection<Guid> serviceEndpointList = new Collection<Guid>();
			Collection<Guid> assemblyList = new Collection<Guid>();
			Collection<Guid> pluginList = new Collection<Guid>();
			Collection<Guid> stepList = new Collection<Guid>();
			Collection<Guid> secureConfigList = new Collection<Guid>();
			Collection<Guid> imageList = new Collection<Guid>();

			//Create the list of various objects that need to be unregistered
			foreach (ICrmEntity entity in crmEntity)
			{
				switch (entity.EntityType)
				{
					case CrmSdk.ServiceEndpoint.EntityLogicalName:
						serviceEndpointList.Add(entity.EntityId);
						break;
					case PluginAssembly.EntityLogicalName:
						assemblyList.Add(entity.EntityId);
						break;
					case PluginType.EntityLogicalName:
						pluginList.Add(entity.EntityId);
						break;
					case SdkMessageProcessingStep.EntityLogicalName:
						stepList.Add(entity.EntityId);
						break;
					case SdkMessageProcessingStepImage.EntityLogicalName:
						imageList.Add(entity.EntityId);
						break;
					default:
						throw new NotImplementedException("Type = " + entity.EntityType.ToString());
				}
			}

			//Retrieve the up-to-date list of steps for the service endpoints and add them to the unregister list
			foreach (Guid stepId in RetrieveStepIdsForServiceEndpoint(org, serviceEndpointList))
			{
				if (!stepList.Contains(stepId))
				{
					stepList.Add(stepId);
				}
			}
			//Retrieve the up-to-date list of plugins for the assemblies and add them to the unregister list
			foreach (Guid pluginId in RetrievePluginIdsForAssembly(org, assemblyList))
			{
				if (!pluginList.Contains(pluginId))
				{
					pluginList.Add(pluginId);
				}
			}

			//Retrieve the up-to-date list of steps for the plugins and add them to the unregister list
			foreach (Guid stepId in RetrieveStepIdsForPlugins(org, pluginList))
			{
				if (!stepList.Contains(stepId))
				{
					stepList.Add(stepId);
				}
			}

			//Retrieve the up-to-date list of secure configs for the steps and add them to the unregister list
			foreach (Guid secureConfigId in RetrieveSecureConfigIdsForStepId(org, stepList))
			{
				if (!secureConfigList.Contains(secureConfigId))
				{
					secureConfigList.Add(secureConfigId);
				}
			}

			//Retrieve the up-to-date list of images for the steps and add them to the unregister list
			foreach (Guid imageId in RetrieveImageIdsForStepId(org, stepList))
			{
				if (!imageList.Contains(imageId))
				{
					imageList.Add(imageId);
				}
			}

			//Loop through each object and delete them
			Dictionary<string, int> deleteStats = new Dictionary<string, int>();
			int totalSteps = secureConfigList.Count + 1;
			if (serviceEndpointList.Count != 0)
			{
				deleteStats.Add(serviceEndpointList.Count == 1 ? "ServiceEndpoint" : "ServiceEndpoints", serviceEndpointList.Count);
				totalSteps += serviceEndpointList.Count;
			}
			if (assemblyList.Count != 0)
			{
				deleteStats.Add(assemblyList.Count == 1 ? "Assembly" : "Assemblies", assemblyList.Count);
				totalSteps += assemblyList.Count;
			}
			if (pluginList.Count != 0)
			{
				deleteStats.Add(pluginList.Count == 1 ? "Plugin" : "Plugins", pluginList.Count);
				totalSteps += pluginList.Count;
			}
			if (stepList.Count != 0)
			{
				deleteStats.Add(stepList.Count == 1 ? "Step" : "Steps", stepList.Count);
				totalSteps += stepList.Count;
			}
			if (imageList.Count != 0)
			{
				deleteStats.Add(imageList.Count == 1 ? "Image" : "Images", imageList.Count);
				totalSteps += imageList.Count;
			}

			try
			{
				if (prog != null)
				{
					prog.Initialize(totalSteps, "Unregistering Images");
				}
				foreach (Guid imageId in imageList)
				{
					org.OrganizationService.Delete(SdkMessageProcessingStepImage.EntityLogicalName, imageId);
					if (prog != null)
					{
						prog.Increment();
					}
				}

				if (prog != null)
				{
					prog.SetText("Unregistering Steps");
				}
				foreach (Guid stepId in stepList)
				{
					org.OrganizationService.Delete(SdkMessageProcessingStep.EntityLogicalName, stepId);
					if (prog != null)
					{
						prog.Increment();
					}
				}

				if (prog != null)
				{
					prog.SetText("Unregistering Secure Configuration");
				}
				foreach (Guid secureConfigId in secureConfigList)
				{
					org.OrganizationService.Delete(SdkMessageProcessingStepSecureConfig.EntityLogicalName, secureConfigId);
					if (prog != null)
					{
						prog.Increment();
					}
				}

				if (prog != null)
				{
					prog.SetText("Unregistering Plugins");
				}
				foreach (Guid pluginId in pluginList)
				{
					org.OrganizationService.Delete(PluginType.EntityLogicalName, pluginId);
					if (prog != null)
					{
						prog.Increment();
					}
				}

				if (prog != null)
				{
					prog.SetText("Unregistering Assemblies");
				}
				foreach (Guid assemblyId in assemblyList)
				{
					org.OrganizationService.Delete(PluginAssembly.EntityLogicalName, assemblyId);
					if (prog != null)
					{
						prog.Increment();
					}
				}

				if (prog != null)
				{
					prog.SetText("Unregistering ServiceEndpoints");
				}
				foreach (Guid serviceEndpointId in serviceEndpointList)
				{
					org.OrganizationService.Delete(CrmSdk.ServiceEndpoint.EntityLogicalName, serviceEndpointId);
					if (prog != null)
					{
						prog.Increment();
					}
				}
			}
			finally
			{
				if (prog != null)
				{
					prog.Complete(true);
				}
			}

			return deleteStats;
		}

		public static List<Guid> RetrieveSecureConfigIdsForStepId(CrmOrganization org, IList<Guid> stepIds)
		{
			return RetrieveReferenceAttributeIds(org, SdkMessageProcessingStep.EntityLogicalName,
				"sdkmessageprocessingstepsecureconfigid", "sdkmessageprocessingstepid", stepIds);
		}
		#endregion

		#region Private Helper Methods
		private static List<Guid> RetrieveStepIdsForServiceEndpoint(CrmOrganization org, IList<Guid> serviceEndpointIds)
		{
			return RetrieveReferenceAttributeIds(org, SdkMessageProcessingStep.EntityLogicalName,
				"sdkmessageprocessingstepid", "eventhandler", serviceEndpointIds);
		}
		private static List<Guid> RetrieveStepIdsForPlugins(CrmOrganization org, IList<Guid> pluginIds)
		{
			return RetrieveReferenceAttributeIds(org, SdkMessageProcessingStep.EntityLogicalName,
				"sdkmessageprocessingstepid", "plugintypeid", pluginIds);
		}

		private static List<Guid> RetrieveImageIdsForStepId(CrmOrganization org, IList<Guid> stepIds)
		{
			return RetrieveReferenceAttributeIds(org, SdkMessageProcessingStepImage.EntityLogicalName,
				"sdkmessageprocessingstepimageid", "sdkmessageprocessingstepid", stepIds);
		}

		private static List<Guid> RetrievePluginIdsForAssembly(CrmOrganization org, IList<Guid> assemblyIds)
		{
			return RetrieveReferenceAttributeIds(org, PluginType.EntityLogicalName,
				"plugintypeid", "pluginassemblyid", assemblyIds);
		}

		/// <summary>
		/// Retrieve the Id for each entityName that has a filterAttribute value in the filterIdList.
		/// </summary>
		private static List<Guid> RetrieveReferenceAttributeIds(CrmOrganization org, string entityName,
			string retrieveAttribute, string filterAttribute, IList<Guid> filterIdList)
		{
			#region Argument Validation
			if (org == null)
			{
				throw new ArgumentNullException("org");
			}
			else if (entityName == null)
			{
				throw new ArgumentNullException("entityName");
			}
			else if (retrieveAttribute == null)
			{
				throw new ArgumentNullException("retrieveAttribute");
			}
			else if (filterAttribute == null)
			{
				throw new ArgumentNullException("filterAttribute");
			}
			else if (retrieveAttribute == filterAttribute)
			{
				throw new ArgumentException("Attributes must be different");
			}
			#endregion

			if (filterIdList.Count == 0)
			{
				return new List<Guid>();
			}

			//Generate the query
			ColumnSet cols = new ColumnSet();
			cols.AddColumns(retrieveAttribute);

			ConditionExpression idCondition = new ConditionExpression();
			idCondition.AttributeName = filterAttribute;
			idCondition.Operator = ConditionOperator.In;
			idCondition.Values.Clear();
			idCondition.Values.AddRange(ConvertIdArrayToObjectArray(filterIdList));

			QueryExpression query = new QueryExpression();
			query.ColumnSet = cols;
			query.Criteria.AddCondition(idCondition);
			query.EntityName = entityName;

			//Loop through the results
			List<Guid> resultList = new List<Guid>();
			foreach (Entity entity in org.OrganizationService.RetrieveMultipleAllPages(query).Entities)
			{
				foreach (System.Collections.Generic.KeyValuePair<string, object> prop in entity.Attributes)
				{
					if (prop.Key == retrieveAttribute)
					{
						Type propType = prop.Value.GetType();
						if (propType == typeof(Guid))
						{
							resultList.Add((Guid)prop.Value);
						}
						else if (propType == typeof(EntityReference))
						{
							resultList.Add(((EntityReference)prop.Value).Id);
						}
						else
						{
							throw new ArgumentException("Unknown property returned " + prop.GetType().FullName);
						}
					}
				}
			}

			return resultList;
		}

		private static object[] ConvertIdArrayToObjectArray(IList<Guid> idList)
		{
			if (idList == null)
			{
				return null;
			}

			List<object> newIdList = new List<object>();
			if (idList != null && idList.Count != 0)
			{
				foreach (Guid id in idList)
				{
					if (id != Guid.Empty)
					{
						newIdList.Add(id);
					}
				}
			}

			return newIdList.ToArray();
		}
		#endregion
	}
}
